﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal class DataQueryContext
  {
    #region Static Fields

    internal static readonly DataQueryContext None = new DataQueryContext();

    #endregion

    #region Constructor

    internal DataQueryContext(
      IEnumerable<SortDescription> sortDescriptions,
      Predicate<object> filterPredicate,
      FilterExpressionBase filterExpression,
      long dataSourceSize )
    {
      if( dataSourceSize < 0 )
        throw new ArgumentOutOfRangeException( "dataSourceSize", "dataSourceSize must be greater than or equal to zero." );

      this.FilterPredicate = filterPredicate;
      this.FilterExpression = filterExpression;
      this.DataSourceSize = dataSourceSize;

      IList<SortDescription> sortOrder = ( sortDescriptions != null )
                                           ? sortDescriptions.ToList()
                                           : new List<SortDescription>( 0 );
      this.SortDescriptions = new ReadOnlyCollection<SortDescription>( sortOrder );
    }

    private DataQueryContext()
      : this( Enumerable.Empty<SortDescription>(), null, null, 0 )
    {
      this.IsNoneContext = true;
    }

    #endregion

    #region FilterPredicate Property

    internal Predicate<object> FilterPredicate
    {
      get;
      private set;
    }

    #endregion

    #region FilterExpression Property

    internal FilterExpressionBase FilterExpression
    {
      get;
      private set;
    }

    #endregion

    #region SortDescriptions Property

    internal IList<SortDescription> SortDescriptions
    {
      get;
      private set;
    }

    #endregion

    #region DataSourceSize Property

    internal long DataSourceSize
    {
      get;
      private set;
    }

    #endregion

    #region IsNoneContext Private Property

    private bool IsNoneContext
    {
      get;
      set;
    }

    #endregion

    #region HashCode Private Property

    private int HashCode
    {
      get
      {
        if( !m_hashCode.HasValue )
        {
          int hash = 17;
          int factor = 37;

          hash = ( hash * factor ) + this.IsNoneContext.GetHashCode();
          hash = ( hash * factor ) + this.DataSourceSize.GetHashCode();

          if( this.FilterPredicate != null )
          {
            hash = ( hash * factor ) + this.FilterPredicate.GetHashCode();
          }

          if( this.FilterExpression != null )
          {
            hash = ( hash * factor ) + this.FilterExpression.GetHashCode();
          }

          foreach( SortDescription sort in this.SortDescriptions )
          {
            hash = ( hash * factor ) + sort.GetHashCode();
          }

          m_hashCode = hash;
        }

        return m_hashCode.Value;
      }
    }

    private Nullable<int> m_hashCode; //null

    #endregion

    public override int GetHashCode()
    {
      return this.HashCode;
    }

    public override bool Equals( object obj )
    {
      if( object.ReferenceEquals( this, obj ) )
        return true;

      DataQueryContext context = obj as DataQueryContext;

      return ( context != null )
          && ( this.GetHashCode() == context.GetHashCode() )
          && ( object.Equals( this.IsNoneContext, context.IsNoneContext ) )
          && ( object.Equals( this.DataSourceSize, context.DataSourceSize ) )
          && ( object.Equals( this.FilterPredicate, context.FilterPredicate ) )
          && ( object.Equals( this.FilterExpression, context.FilterExpression ) )
          && ( this.SortDescriptions.SequenceEqual( context.SortDescriptions ) );
    }

    internal bool IsSimilarContext( DataQueryContext context, out bool reversedSortOrder )
    {
      reversedSortOrder = false;

      if( object.ReferenceEquals( this, context ) )
        return true;

      if( ( context == null )
        || ( !object.Equals( this.FilterPredicate, context.FilterPredicate ) )
        || ( !object.Equals( this.FilterExpression, context.FilterExpression ) )
        || ( !object.Equals( this.SortDescriptions.Count, context.SortDescriptions.Count ) ) )
        return false;

      // Check if the sort order is the same or reversed.
      int count = this.SortDescriptions.Count;
      Nullable<bool> reversed = null;

      for( int i = 0; i < count; i++ )
      {
        SortDescription firstSort = this.SortDescriptions[ i ];
        SortDescription secondSort = context.SortDescriptions[ i ];

        // The two contexts aren't sorted on the same fields.
        if( firstSort.PropertyName != secondSort.PropertyName )
          return false;

        bool isReversed = ( firstSort.Direction != secondSort.Direction );

        if( reversed.HasValue )
        {
          // Make sure the two contexts are either sorted in the same order
          // or reverse order.
          if( reversed.Value != isReversed )
            return false;
        }
        else
        {
          reversed = isReversed;
        }
      }

      reversedSortOrder = reversed.GetValueOrDefault( false );

      return true;
    }
  }
}
